1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.cache;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20
21 import com.google.common.annotations.Beta;
22 import com.google.common.annotations.VisibleForTesting;
23 import com.google.common.base.MoreObjects;
24 import com.google.common.base.Objects;
25 import com.google.common.base.Splitter;
26 import com.google.common.cache.LocalCache.Strength;
27 import com.google.common.collect.ImmutableList;
28 import com.google.common.collect.ImmutableMap;
29
30 import java.util.List;
31 import java.util.concurrent.TimeUnit;
32
33 import javax.annotation.Nullable;
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 @Beta
83 public final class CacheBuilderSpec {
84
85 private interface ValueParser {
86 void parse(CacheBuilderSpec spec, String key, @Nullable String value);
87 }
88
89
90 private static final Splitter KEYS_SPLITTER = Splitter.on(',').trimResults();
91
92
93 private static final Splitter KEY_VALUE_SPLITTER = Splitter.on('=').trimResults();
94
95
96 private static final ImmutableMap<String, ValueParser> VALUE_PARSERS =
97 ImmutableMap.<String, ValueParser>builder()
98 .put("initialCapacity", new InitialCapacityParser())
99 .put("maximumSize", new MaximumSizeParser())
100 .put("maximumWeight", new MaximumWeightParser())
101 .put("concurrencyLevel", new ConcurrencyLevelParser())
102 .put("weakKeys", new KeyStrengthParser(Strength.WEAK))
103 .put("softValues", new ValueStrengthParser(Strength.SOFT))
104 .put("weakValues", new ValueStrengthParser(Strength.WEAK))
105 .put("recordStats", new RecordStatsParser())
106 .put("expireAfterAccess", new AccessDurationParser())
107 .put("expireAfterWrite", new WriteDurationParser())
108 .put("refreshAfterWrite", new RefreshDurationParser())
109 .put("refreshInterval", new RefreshDurationParser())
110 .build();
111
112 @VisibleForTesting Integer initialCapacity;
113 @VisibleForTesting Long maximumSize;
114 @VisibleForTesting Long maximumWeight;
115 @VisibleForTesting Integer concurrencyLevel;
116 @VisibleForTesting Strength keyStrength;
117 @VisibleForTesting Strength valueStrength;
118 @VisibleForTesting Boolean recordStats;
119 @VisibleForTesting long writeExpirationDuration;
120 @VisibleForTesting TimeUnit writeExpirationTimeUnit;
121 @VisibleForTesting long accessExpirationDuration;
122 @VisibleForTesting TimeUnit accessExpirationTimeUnit;
123 @VisibleForTesting long refreshDuration;
124 @VisibleForTesting TimeUnit refreshTimeUnit;
125
126 private final String specification;
127
128 private CacheBuilderSpec(String specification) {
129 this.specification = specification;
130 }
131
132
133
134
135
136
137 public static CacheBuilderSpec parse(String cacheBuilderSpecification) {
138 CacheBuilderSpec spec = new CacheBuilderSpec(cacheBuilderSpecification);
139 if (!cacheBuilderSpecification.isEmpty()) {
140 for (String keyValuePair : KEYS_SPLITTER.split(cacheBuilderSpecification)) {
141 List<String> keyAndValue = ImmutableList.copyOf(KEY_VALUE_SPLITTER.split(keyValuePair));
142 checkArgument(!keyAndValue.isEmpty(), "blank key-value pair");
143 checkArgument(keyAndValue.size() <= 2,
144 "key-value pair %s with more than one equals sign", keyValuePair);
145
146
147 String key = keyAndValue.get(0);
148 ValueParser valueParser = VALUE_PARSERS.get(key);
149 checkArgument(valueParser != null, "unknown key %s", key);
150
151 String value = keyAndValue.size() == 1 ? null : keyAndValue.get(1);
152 valueParser.parse(spec, key, value);
153 }
154 }
155
156 return spec;
157 }
158
159
160
161
162 public static CacheBuilderSpec disableCaching() {
163
164 return CacheBuilderSpec.parse("maximumSize=0");
165 }
166
167
168
169
170 CacheBuilder<Object, Object> toCacheBuilder() {
171 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
172 if (initialCapacity != null) {
173 builder.initialCapacity(initialCapacity);
174 }
175 if (maximumSize != null) {
176 builder.maximumSize(maximumSize);
177 }
178 if (maximumWeight != null) {
179 builder.maximumWeight(maximumWeight);
180 }
181 if (concurrencyLevel != null) {
182 builder.concurrencyLevel(concurrencyLevel);
183 }
184 if (keyStrength != null) {
185 switch (keyStrength) {
186 case WEAK:
187 builder.weakKeys();
188 break;
189 default:
190 throw new AssertionError();
191 }
192 }
193 if (valueStrength != null) {
194 switch (valueStrength) {
195 case SOFT:
196 builder.softValues();
197 break;
198 case WEAK:
199 builder.weakValues();
200 break;
201 default:
202 throw new AssertionError();
203 }
204 }
205 if (recordStats != null && recordStats) {
206 builder.recordStats();
207 }
208 if (writeExpirationTimeUnit != null) {
209 builder.expireAfterWrite(writeExpirationDuration, writeExpirationTimeUnit);
210 }
211 if (accessExpirationTimeUnit != null) {
212 builder.expireAfterAccess(accessExpirationDuration, accessExpirationTimeUnit);
213 }
214 if (refreshTimeUnit != null) {
215 builder.refreshAfterWrite(refreshDuration, refreshTimeUnit);
216 }
217
218 return builder;
219 }
220
221
222
223
224
225
226
227 public String toParsableString() {
228 return specification;
229 }
230
231
232
233
234
235 @Override
236 public String toString() {
237 return MoreObjects.toStringHelper(this).addValue(toParsableString()).toString();
238 }
239
240 @Override
241 public int hashCode() {
242 return Objects.hashCode(
243 initialCapacity,
244 maximumSize,
245 maximumWeight,
246 concurrencyLevel,
247 keyStrength,
248 valueStrength,
249 recordStats,
250 durationInNanos(writeExpirationDuration, writeExpirationTimeUnit),
251 durationInNanos(accessExpirationDuration, accessExpirationTimeUnit),
252 durationInNanos(refreshDuration, refreshTimeUnit));
253 }
254
255 @Override
256 public boolean equals(@Nullable Object obj) {
257 if (this == obj) {
258 return true;
259 }
260 if (!(obj instanceof CacheBuilderSpec)) {
261 return false;
262 }
263 CacheBuilderSpec that = (CacheBuilderSpec) obj;
264 return Objects.equal(initialCapacity, that.initialCapacity)
265 && Objects.equal(maximumSize, that.maximumSize)
266 && Objects.equal(maximumWeight, that.maximumWeight)
267 && Objects.equal(concurrencyLevel, that.concurrencyLevel)
268 && Objects.equal(keyStrength, that.keyStrength)
269 && Objects.equal(valueStrength, that.valueStrength)
270 && Objects.equal(recordStats, that.recordStats)
271 && Objects.equal(durationInNanos(writeExpirationDuration, writeExpirationTimeUnit),
272 durationInNanos(that.writeExpirationDuration, that.writeExpirationTimeUnit))
273 && Objects.equal(durationInNanos(accessExpirationDuration, accessExpirationTimeUnit),
274 durationInNanos(that.accessExpirationDuration, that.accessExpirationTimeUnit))
275 && Objects.equal(durationInNanos(refreshDuration, refreshTimeUnit),
276 durationInNanos(that.refreshDuration, that.refreshTimeUnit));
277 }
278
279
280
281
282
283 @Nullable private static Long durationInNanos(long duration, @Nullable TimeUnit unit) {
284 return (unit == null) ? null : unit.toNanos(duration);
285 }
286
287
288 abstract static class IntegerParser implements ValueParser {
289 protected abstract void parseInteger(CacheBuilderSpec spec, int value);
290
291 @Override
292 public void parse(CacheBuilderSpec spec, String key, String value) {
293 checkArgument(value != null && !value.isEmpty(), "value of key %s omitted", key);
294 try {
295 parseInteger(spec, Integer.parseInt(value));
296 } catch (NumberFormatException e) {
297 throw new IllegalArgumentException(
298 String.format("key %s value set to %s, must be integer", key, value), e);
299 }
300 }
301 }
302
303
304 abstract static class LongParser implements ValueParser {
305 protected abstract void parseLong(CacheBuilderSpec spec, long value);
306
307 @Override
308 public void parse(CacheBuilderSpec spec, String key, String value) {
309 checkArgument(value != null && !value.isEmpty(), "value of key %s omitted", key);
310 try {
311 parseLong(spec, Long.parseLong(value));
312 } catch (NumberFormatException e) {
313 throw new IllegalArgumentException(
314 String.format("key %s value set to %s, must be integer", key, value), e);
315 }
316 }
317 }
318
319
320 static class InitialCapacityParser extends IntegerParser {
321 @Override
322 protected void parseInteger(CacheBuilderSpec spec, int value) {
323 checkArgument(spec.initialCapacity == null,
324 "initial capacity was already set to ", spec.initialCapacity);
325 spec.initialCapacity = value;
326 }
327 }
328
329
330 static class MaximumSizeParser extends LongParser {
331 @Override
332 protected void parseLong(CacheBuilderSpec spec, long value) {
333 checkArgument(spec.maximumSize == null,
334 "maximum size was already set to ", spec.maximumSize);
335 checkArgument(spec.maximumWeight == null,
336 "maximum weight was already set to ", spec.maximumWeight);
337 spec.maximumSize = value;
338 }
339 }
340
341
342 static class MaximumWeightParser extends LongParser {
343 @Override
344 protected void parseLong(CacheBuilderSpec spec, long value) {
345 checkArgument(spec.maximumWeight == null,
346 "maximum weight was already set to ", spec.maximumWeight);
347 checkArgument(spec.maximumSize == null,
348 "maximum size was already set to ", spec.maximumSize);
349 spec.maximumWeight = value;
350 }
351 }
352
353
354 static class ConcurrencyLevelParser extends IntegerParser {
355 @Override
356 protected void parseInteger(CacheBuilderSpec spec, int value) {
357 checkArgument(spec.concurrencyLevel == null,
358 "concurrency level was already set to ", spec.concurrencyLevel);
359 spec.concurrencyLevel = value;
360 }
361 }
362
363
364 static class KeyStrengthParser implements ValueParser {
365 private final Strength strength;
366
367 public KeyStrengthParser(Strength strength) {
368 this.strength = strength;
369 }
370
371 @Override
372 public void parse(CacheBuilderSpec spec, String key, @Nullable String value) {
373 checkArgument(value == null, "key %s does not take values", key);
374 checkArgument(spec.keyStrength == null, "%s was already set to %s", key, spec.keyStrength);
375 spec.keyStrength = strength;
376 }
377 }
378
379
380 static class ValueStrengthParser implements ValueParser {
381 private final Strength strength;
382
383 public ValueStrengthParser(Strength strength) {
384 this.strength = strength;
385 }
386
387 @Override
388 public void parse(CacheBuilderSpec spec, String key, @Nullable String value) {
389 checkArgument(value == null, "key %s does not take values", key);
390 checkArgument(spec.valueStrength == null,
391 "%s was already set to %s", key, spec.valueStrength);
392
393 spec.valueStrength = strength;
394 }
395 }
396
397
398 static class RecordStatsParser implements ValueParser {
399
400 @Override
401 public void parse(CacheBuilderSpec spec, String key, @Nullable String value) {
402 checkArgument(value == null, "recordStats does not take values");
403 checkArgument(spec.recordStats == null, "recordStats already set");
404 spec.recordStats = true;
405 }
406 }
407
408
409 abstract static class DurationParser implements ValueParser {
410 protected abstract void parseDuration(
411 CacheBuilderSpec spec,
412 long duration,
413 TimeUnit unit);
414
415 @Override
416 public void parse(CacheBuilderSpec spec, String key, String value) {
417 checkArgument(value != null && !value.isEmpty(), "value of key %s omitted", key);
418 try {
419 char lastChar = value.charAt(value.length() - 1);
420 TimeUnit timeUnit;
421 switch (lastChar) {
422 case 'd':
423 timeUnit = TimeUnit.DAYS;
424 break;
425 case 'h':
426 timeUnit = TimeUnit.HOURS;
427 break;
428 case 'm':
429 timeUnit = TimeUnit.MINUTES;
430 break;
431 case 's':
432 timeUnit = TimeUnit.SECONDS;
433 break;
434 default:
435 throw new IllegalArgumentException(
436 String.format("key %s invalid format. was %s, must end with one of [dDhHmMsS]",
437 key, value));
438 }
439
440 long duration = Long.parseLong(value.substring(0, value.length() - 1));
441 parseDuration(spec, duration, timeUnit);
442 } catch (NumberFormatException e) {
443 throw new IllegalArgumentException(
444 String.format("key %s value set to %s, must be integer", key, value));
445 }
446 }
447 }
448
449
450 static class AccessDurationParser extends DurationParser {
451 @Override protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit) {
452 checkArgument(spec.accessExpirationTimeUnit == null, "expireAfterAccess already set");
453 spec.accessExpirationDuration = duration;
454 spec.accessExpirationTimeUnit = unit;
455 }
456 }
457
458
459 static class WriteDurationParser extends DurationParser {
460 @Override protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit) {
461 checkArgument(spec.writeExpirationTimeUnit == null, "expireAfterWrite already set");
462 spec.writeExpirationDuration = duration;
463 spec.writeExpirationTimeUnit = unit;
464 }
465 }
466
467
468 static class RefreshDurationParser extends DurationParser {
469 @Override protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit) {
470 checkArgument(spec.refreshTimeUnit == null, "refreshAfterWrite already set");
471 spec.refreshDuration = duration;
472 spec.refreshTimeUnit = unit;
473 }
474 }
475 }